//
// This file is part of cpp-lib, a library of C++ functions and classes.
// Cpp-lib is copyright (C) 2004 and onwards, The CPL Interest Group.
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or (at
// your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library (see the file COPYING); if not, write to the
// Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
// 02111-1307  USA
//
// $Id$
//

#include <string>
#include <sstream>
#include <exception>
#include <stdexcept>
#include <iostream>

#include <cstring>
#include <cstdlib>
#include <cassert>
#include <cerrno>
#include <climits>

#include "boost/lexical_cast.hpp"
#include "boost/any.hpp"
#include "boost/cast.hpp"


#include "cpp-lib/util.h"
#include "cpp-lib/network.h"

using namespace cpl::util::network ;
using namespace cpl::util          ;
using namespace cpl::detail_       ;

#include "cpp-lib/platform/net_impl.h"


namespace {
 


//
// Enable certain SO_* options (like SO_REUSEADDR, SO_BROADCAST,...)
// or disable again with false
//

template< typename T >
void enable_sockopt
( socketfd_t const fd , T const& option, bool enable = true ) {

  const int optval = enable ? 1 : 0 ;

  if(
    ::setsockopt(
      fd ,
      SOL_SOCKET ,
      option ,
      reinterpret_cast< char const* >( &optval ) ,
      sizeof( optval )
    ) == -1
  ) { throw_socket_error( "setsockopt" ) ; }

}


void my_listen( socketfd_t const fd , int const backlog ) {

  int ret ;
  do 
  { ret = ::listen( fd , backlog ) ; } 
  while( EINTR_repeat( ret ) ) ;

  if( ret < 0 )
  { throw_socket_error( "listen" ) ; }

}


template< int type >
void my_bind( socketfd_t const fd , address< type > const& a ) {

  int ret ;
  do { ret = ::bind( fd , a.sockaddr_pointer() , a.length() ) ; } 
  while( EINTR_repeat( ret ) ) ;

  if( ret < 0 )
  { throw_socket_error( "bind" ) ; }
  
}


template< int type >
void my_connect( socketfd_t const fd , address< type > const& a ) {

  int ret ;
  do 
  { ret = ::connect( fd , a.sockaddr_pointer() , a.length() ) ; }
  while( EINTR_repeat( ret ) ) ;

  if( ret < 0 )
  { throw_socket_error( "connect" ) ; }

}


template< int type >
address< type > my_getsockname( socketfd_t const fd ) {

  address< type > a ;
  int const err = 
    ::getsockname( fd , a.sockaddr_pointer() , a.socklen_pointer() ) ;

  if( err < 0 )
  { throw_socket_error( "getsockname" ) ; }

  return a ;

}


template< int type >
address< type > my_getpeername( socketfd_t const fd ) {

  address< type > a ;
  int const err = 
    ::getpeername( fd , a.sockaddr_pointer() , a.socklen_pointer() ) ;

  if( err < 0 )
  { throw_socket_error( "getpeername" ) ; }

  return a ;

}


socketfd_t const my_accept( socketfd_t const fd ) {

  // Don't care where the connection request comes from...

  socketfd_t ret ;
  do { ret = ::accept( fd , 0 , 0 ) ; } 
  while( EINTR_repeat( ret ) ) ;

  if( invalid_socket == ret ) { throw_socket_error( "accept" ) ; }

  return ret ;

}

} // end anonymous namespace


void cpl::detail_::throw_socket_error( std::string const& msg )
{ throw std::runtime_error( msg + ": " + SOCKET_ERROR_MSG ) ; }


long cpl::detail_::streambuf_traits< cpl::util::network::connection >::read
( resource_type& r , char      * const buf , long const n ) {
  
  long ret ;
  do { ret = ::recv( r.fd() , buf , n , 0 ) ; } 
  while( EINTR_repeat( ret ) ) ;

  assert( -1 <= ret      ) ;
  assert(       ret <= n ) ;

  return ret ;

}


void cpl::detail_::streambuf_traits< cpl::util::network::connection >
::shutdown_read( resource_type& r ) { r.shutdown_read() ; }
  
void cpl::detail_::streambuf_traits< cpl::util::network::connection >
::shutdown_write( resource_type& r ) { r.shutdown_write() ; }


long cpl::detail_::streambuf_traits< cpl::util::network::connection >::write
( resource_type& r , char const* const buf , long const n ) {
  
  long ret ;

  // TODO: partial send
  do { ret = ::send( r.fd() , buf , n , 0 ) ; } 
  while( EINTR_repeat( ret ) ) ;

  if( ret < 0 ) 
  { return -1 ; }

  assert( n == ret ) ;
  return ret ;

}


socketfd_t cpl::detail_::socket_resource_traits::invalid()
{ return invalid_socket ; }

bool cpl::detail_::socket_resource_traits::valid( socketfd_t const s )
{ return invalid() != s ; }


//
// TODO: FAQ: The proper sequence for closing a TCP connection gracefully is:
//   1. Finish sending data.
//   2. Call shutdown() with the how parameter set to 1 (SHUT_WR).
//   3. Loop on receive() until it returns 0.
//   4. Call closesocket().
//

void cpl::detail_::socket_resource_traits::dispose
( socketfd_t const s ) { 

  if( socketclose( s ) < 0 )
  { die( "socket close(): " + SOCKET_ERROR_MSG ) ; }

} ;


void cpl::util::network::acceptor::initialize
( address_list_type const& la , int const bl ) {

  std::string err ;

  if( 0 == la.size() ) 
  { throw std::runtime_error
    ( "acceptor: must give at least one local address" ) ; }

  for( address_list_type::const_iterator j  = la.begin() ; 
                                         j != la.end  () ; 
                                         ++j ) {

    try {

      s.reset( new stream_socket( j->family() ) ) ;

      my_bind( s->fd() , *j ) ;
      my_listen( s->fd() , bl ) ;

      local_ = *j ;

      return ;

    } catch( std::exception const& e ) { err = e.what() ; continue ; }

  }

  throw std::runtime_error( err ) ;

}


cpl::util::network::acceptor::acceptor
( address_list_type const& la , int const bl ) {

  initialize( la , bl ) ; 
  always_assert( s.get() ) ; 

}


cpl::util::network::acceptor::acceptor
( std::string const& ls , int const bl ) {
  
  initialize( resolve_stream( ls ) , bl ) ;
  always_assert( s.get() ) ; 

}


void cpl::util::network::connection::initialize( 
  address_list_type const& ra ,
  address_list_type const& la
) {

  std::string err ;

  if( 0 == la.size() ) {

    for( address_list_type::const_iterator j  = ra.begin() ; 
                                           j != ra.end  () ; 
                                           ++j ) {

      try {

        s.reset( new stream_socket( j->family() ) ) ;
        my_connect( s->fd() , *j ) ;

        goto OK ;

      } catch( std::exception const& e ) { err = e.what() ; continue ; }

    }

  } // if( 0 == la.size() )
  else 
  {

    for( address_list_type::const_iterator i  = la.begin() ; 
                                           i != la.end  () ; 
                                           ++i ) {

      for( address_list_type::const_iterator j  = ra.begin() ; 
                                             j != ra.end  () ; 
                                             ++j ) {

        if( i->family() != j->family() ) 
        { err = "address families don't match" ; continue ; }

        try {

          s.reset( new stream_socket( i->family() ) ) ; 

          my_bind   ( s->fd() , *i ) ;
          my_connect( s->fd() , *j ) ;

          goto OK ;

        } catch( std::exception const& e ) { err = e.what() ; continue ; }

      }

    }

  }

  throw std::runtime_error( err ) ;

OK:

  always_assert( s.get() ) ;
  initialize_local_peer() ;

}
  

void cpl::util::network::connection::initialize_local_peer() { 

  always_assert( s.get() ) ;

  local_ = my_getsockname< SOCK_STREAM >( s->fd() ) ; 
  peer_  = my_getpeername< SOCK_STREAM >( s->fd() ) ; 

}


cpl::util::network::connection::connection
( std::string const& n , std::string const& serv ) {

  initialize( resolve_stream( n , serv ) , address_list_type() ) ;

}


cpl::util::network::connection::connection( 
  address_list_type const& ra ,
  address_list_type const& la
) { 
  
  initialize( ra , la ) ; 

}


cpl::util::network::connection::connection
( acceptor& a ) { 
  
  s.reset( new stream_socket( 4711 , my_accept( a.fd() ) ) ) ; 
  initialize_local_peer() ;

}


void cpl::util::network::connection::no_delay( bool b )
{ enable_sockopt( fd() , TCP_NODELAY , b ) ; }


cpl::util::network::datagram_receiver::datagram_receiver
( address_list_type const& la ) {

  initialize( la ) ;

}


cpl::util::network::datagram_receiver::datagram_receiver
( std::string const& ls ) {
  
  initialize( resolve_datagram( ls ) ) ;

}


//
// TODO: templatize on socket type and use also in acceptor::initialize.
//

std::auto_ptr< cpl::detail_::datagram_socket >
bound_datagram_socket( datagram_address_list const& la ) {

  std::string err ;

  if( 0 == la.size() ) 
  { throw std::runtime_error
    ( "must give at least one local address" ) ; }

  for( datagram_address_list::const_iterator j  = la.begin() ; 
                                             j != la.end  () ; 
                                           ++j ) {

    try {

      std::auto_ptr< datagram_socket >
        s( new datagram_socket( j->family() ) ) ;

      my_bind( s->fd() , *j ) ;

      return s ;

    } catch( std::exception const& e ) { err = e.what() ; continue ; }

  }

  throw std::runtime_error( err ) ;

}



void cpl::util::network::datagram_receiver::initialize
( address_list_type const& la ) {

  try { s = bound_datagram_socket( la ) ; } 
  
  catch( std::runtime_error const& e ) {

    throw 
      std::runtime_error( std::string( "datagram_receiver: " ) + e.what() ) ;

  }

  local_ = my_getsockname< SOCK_DGRAM >( s->fd() ) ;
  
  enable_sockopt( s->fd() , SO_BROADCAST ) ;
  enable_sockopt( s->fd() , SO_REUSEADDR ) ;

}


cpl::util::network::datagram_sender::datagram_sender() {}

cpl::util::network::datagram_sender::datagram_sender( std::string const& ls ) 
{ initialize( resolve_datagram( ls ) ) ; }

cpl::util::network::datagram_sender::datagram_sender(
  address_list_type const& la 
) 
{ initialize( la ) ; }


void cpl::util::network::datagram_sender::initialize( 
  address_list_type const& la 
) {
  
  try { s = bound_datagram_socket( la ) ; } 
  
  catch( std::runtime_error const& e ) {

    throw 
      std::runtime_error( std::string( "datagram_sender: " ) + e.what() ) ;

  }

  local_ = my_getsockname< SOCK_DGRAM >( s->fd() ) ;

}
